//
//  YMPDFCatalog.m
//  TestDemo
//
//  Created by fantasy on 16/6/26.
//  Copyright © 2016年 goappbox. All rights reserved.
//

#import "YMPDFCatalog.h"
#import <UIKit/UIKit.h>
#import "NSObject+PDF.h"

@interface YMPDFCatalog ()
{
    CGPDFObjectType type;
    CGPDFObjectRef object;
    CGPDFDictionaryRef catalog;
    NSString *name;
    NSMutableArray *children;
}
@property (nonatomic, strong) UIImage *image;
@end

static UIImage * getImage(CGPDFObjectRef object)
{
    CFDataRef data;
    CGPDFArrayRef array;
    CGPDFObjectType type;
    CGPDFStreamRef stream;
    CGPDFDataFormat format;
    CGPDFDictionaryRef dict;
    CGPDFInteger width, height, bps, spp;
    unsigned char *imageData[1];
    NSString *colorSpace;
    const char *name;
    
    type = CGPDFObjectGetType(object);
    if (type != kCGPDFObjectTypeStream)
        return nil;
    
    CGPDFObjectGetValue(object, kCGPDFObjectTypeStream, &stream);
    dict = CGPDFStreamGetDictionary(stream);
    if (!CGPDFDictionaryGetName(dict, "Subtype", &name))
        return nil;
    if (strcmp(name, "Image") != 0)
        return nil;
    if (!CGPDFDictionaryGetInteger(dict, "Width", &width))
        return nil;
    if (!CGPDFDictionaryGetInteger(dict, "Height", &height))
        return nil;
    if (!CGPDFDictionaryGetInteger(dict, "BitsPerComponent", &bps))
        return nil;
    
    /* We're a little sloppy here---we should really handle the full set of
     * color spaces.  This only handles the basic gray, RGB, and CMYK
     * ones. */
    
    if (CGPDFDictionaryGetArray(dict, "ColorSpace", &array)) {
        if (!CGPDFArrayGetStream(array, 1, &stream))
            return nil;
        dict = CGPDFStreamGetDictionary(stream);
        CGPDFDictionaryGetInteger(dict, "N", &spp);
        switch (spp) {
            case 1:
                colorSpace = @"NSDeviceBlackColorSpace";
                break;
            case 3:
                colorSpace = @"NSDeviceRGBColorSpace";
                break;
            case 4:
                colorSpace = @"NSDeviceCMYKColorSpace";
                break;
            default:
                return nil;
        }
    } else {
        CGPDFDictionaryGetName(dict, "ColorSpace", &name);
        if (strcmp(name, "DeviceRGB") == 0) {
            colorSpace = @"NSDeviceRGBColorSpace";
            spp = 3;
        } else if (strcmp(name, "DeviceCMYK") == 0) {
            colorSpace = @"NSDeviceCMYKColorSpace";
            spp = 4;
        } else if (strcmp(name, "DeviceGray") == 0) {
            colorSpace = @"NSDeviceBlackColorSpace";
            spp = 1;
        } else {
            return nil;
        }
    }
    
    /* We need to handle JPEG and JPEG2000.  Of course, ideally AppKit
     * would handle CGImages directly.... */
    
    data = CGPDFStreamCopyData(stream, &format);
    if (format != CGPDFDataFormatRaw)
        return nil;
    
    imageData[0] = (unsigned char *)CFDataGetBytePtr(data);
    //    return [[NSBitmapImageRep alloc]
    //            initWithBitmapDataPlanes:imageData
    //            pixelsWide:width pixelsHigh:height
    //            bitsPerSample:bps samplesPerPixel:spp
    //            hasAlpha:NO isPlanar:NO colorSpaceName:colorSpace
    //            bytesPerRow:0 bitsPerPixel:0];
    
    return nil;
}

static void
addItems(const char *key, CGPDFObjectRef object, void *info)
{
    NSString *string;
    NSMutableArray *children;
    YMPDFCatalog *node;
    
    children = (__bridge NSMutableArray *)(info);
    string = [[NSString alloc] initWithFormat:@"/%s", key];
    node = [[YMPDFCatalog alloc] initWithObject:object name:string];
    if (node != nil) {
        [children addObject:node];
    }
}

@implementation YMPDFCatalog
- (instancetype) initWithDictionary:(CGPDFDictionaryRef)dictionary {
    self = [super init];
    if (self) {
        type = kCGPDFObjectTypeDictionary;
        catalog = dictionary;
    }
    return self;
}

- (instancetype) initWithObject:(CGPDFObjectRef)obj name:(NSString *)string {
    if (self = [super init]) {
        object = obj;
        name = [string copy];
        type = CGPDFObjectGetType(object);
        self.image = getImage(object);
    }
    return self;
}

- (void)setPDFObject:(CGPDFObjectRef)obj forKey:(NSString *)key {
//    Type,Pages,Outlines
    if (children == nil) {
        children = [[NSMutableArray alloc] init];
    }
    
    [children addObject:[[YMPDFCatalog alloc] initWithObject:obj name:key]];
}

- (NSString *)name
{
    return name;
}

- (CGPDFObjectType)type
{
    return type;
}

- (NSString *)typeString
{
    switch (type) {
        case kCGPDFObjectTypeBoolean:
            return @"Boolean";
        case kCGPDFObjectTypeInteger:
            return @"Integer";
        case kCGPDFObjectTypeReal:
            return @"Real";
        case kCGPDFObjectTypeName:
            return @"Name";
        case kCGPDFObjectTypeString:
            return @"String";
        case kCGPDFObjectTypeArray:
            return @"Array";
        case kCGPDFObjectTypeDictionary:
            return @"Dictionary";
        case kCGPDFObjectTypeStream:
            return @"Stream";
        case kCGPDFObjectTypeNull:
        default:
            return nil;
    }
}


- (NSString *)value
{
    const char *n = NULL;
    CGPDFReal real;
    CGPDFInteger integer;
    CGPDFBoolean boolean;
    CGPDFStringRef string;
    
    switch (type) {
        case kCGPDFObjectTypeNull:
            return @"null";
            
        case kCGPDFObjectTypeBoolean:
            CGPDFObjectGetValue(object, type, &boolean);
            return boolean ? @"true" : @"false";
            
        case kCGPDFObjectTypeInteger:
            CGPDFObjectGetValue(object, type, &integer);
            return [NSString stringWithFormat:@"%d", (int)integer];
            
        case kCGPDFObjectTypeReal:
            CGPDFObjectGetValue(object, type, &real);
            return [NSString stringWithFormat:@"%g", (double)real];
            
        case kCGPDFObjectTypeName:
            CGPDFObjectGetValue(object, type, &n);
            return [NSString stringWithFormat:@"/%s", n];
            
        case kCGPDFObjectTypeString:
            CGPDFObjectGetValue(object, type, &string);
            return CFBridgingRelease(CGPDFStringCopyTextString(string));
            
        case kCGPDFObjectTypeArray:
        case kCGPDFObjectTypeDictionary:
        case kCGPDFObjectTypeStream:
        default:
            return @"";
    }
}

- (NSArray *)children
{
    size_t k, count;
    CGPDFObjectRef obj;
    CGPDFArrayRef array;
    CGPDFStreamRef stream;
    CGPDFDictionaryRef dict;
    NSString *string;
    YMPDFCatalog *node;
    
    if (children != nil)
        return children;
    
    switch (type) {
        case kCGPDFObjectTypeArray:
            CGPDFObjectGetValue(object, kCGPDFObjectTypeArray, &array);
            count = CGPDFArrayGetCount(array);
            children = [[NSMutableArray alloc] initWithCapacity:count];
            for (k = 0; k < count; k++) {
                CGPDFArrayGetObject(array, k, &obj);
                string = [[NSString alloc] initWithFormat:@"%d", (int)k];
                node = [[YMPDFCatalog alloc] initWithObject:obj name:string];
                if (node != nil) {
                    [children addObject:node];
                }
            }
            break;
            
        case kCGPDFObjectTypeDictionary:
            if (catalog != nil) {
                dict = catalog;
            } else {
                CGPDFObjectGetValue(object, kCGPDFObjectTypeDictionary, &dict);
            }
            count = CGPDFDictionaryGetCount(dict);
            children = [[NSMutableArray alloc] initWithCapacity:count];
            CGPDFDictionaryApplyFunction(dict, &addItems, (__bridge void * _Nullable)(children));
            break;
            
        case kCGPDFObjectTypeStream:
            CGPDFObjectGetValue(object, kCGPDFObjectTypeStream, &stream);
            dict = CGPDFStreamGetDictionary(stream);
            count = CGPDFDictionaryGetCount(dict);
            children = [[NSMutableArray alloc] initWithCapacity:count];
            CGPDFDictionaryApplyFunction(dict, &addItems, (__bridge void * _Nullable)(children));
            break;
            
        default:
            return nil;
    }
    
    if (_image != nil) {
        count = [children count];
        for (k = 0; k < count; k++)
            [(YMPDFCatalog *)[children objectAtIndex:k] setImage:_image];
    }
    
    return children;
}

- (NSAttributedString *)arrayInfo
{
    size_t k, count;
    YMPDFCatalog *node;
    NSMutableAttributedString *string;
    NSArray *array;
    
    string = [[NSMutableAttributedString alloc] init];
    
    [string appendString:@"[ "];
    
    array = [self children];
    count = [array count];
    for (k = 0; k < count; k++) {
        node = [array objectAtIndex:k];
        switch ([node type]) {
            case kCGPDFObjectTypeNull:
            case kCGPDFObjectTypeBoolean:
            case kCGPDFObjectTypeInteger:
            case kCGPDFObjectTypeReal:
            case kCGPDFObjectTypeName:
            case kCGPDFObjectTypeString:
                [string appendString:[node value]];
                break;
            case kCGPDFObjectTypeArray:
                [string appendAttributedString:[@"array" italicize]];
                break;
            case kCGPDFObjectTypeDictionary:
                [string appendAttributedString:[@"dictionary" italicize]];
                break;
            case kCGPDFObjectTypeStream:
                [string appendAttributedString:[@"stream" italicize]];
                break;
            default:
                [string appendAttributedString:[@"unknown" italicize]];
                break;
        }
        [string appendString:@" "];
    }
    [string appendString:@"]"];
    
    return string;
}

- (NSAttributedString *)dictionaryInfo
{
    NSArray *array;
    YMPDFCatalog *node;
    NSMutableAttributedString *string;
    size_t k, count;
    
    string = [[NSMutableAttributedString alloc] init];
    
    [string appendString:@"<<\n"];
    
    array = [self children];
    count = [array count];
    for (k = 0; k < count; k++) {
        node = [array objectAtIndex:k];
        [string appendString:@"  "];
        [string appendString:[node name]];
        [string appendString:@" "];
        switch ([node type]) {
            case kCGPDFObjectTypeNull:
            case kCGPDFObjectTypeBoolean:
            case kCGPDFObjectTypeInteger:
            case kCGPDFObjectTypeReal:
            case kCGPDFObjectTypeName:
            case kCGPDFObjectTypeString:
                [string appendString:[node value]];
                break;
            case kCGPDFObjectTypeArray:
                [string appendAttributedString:[@"array" italicize]];
                break;
            case kCGPDFObjectTypeDictionary:
                [string appendAttributedString:[@"dictionary" italicize]];
                break;
            case kCGPDFObjectTypeStream:
                [string appendAttributedString:[@"stream" italicize]];
                break;
            default:
                [string appendAttributedString:[@"unknown" italicize]];
                break;
        }
        [string appendString:@"\n"];
    }
    [string appendString:@">>"];
    
    return string;
}

- (NSAttributedString *)streamInfo
{
    NSData *data;
    NSString *string;
    CGPDFStreamRef stream;
    CGPDFDataFormat constant;
    
    CGPDFObjectGetValue(object, kCGPDFObjectTypeStream, &stream);
    data = CFBridgingRelease(CGPDFStreamCopyData(stream, &constant));
    string = [NSString stringWithData:data encoding:NSASCIIStringEncoding];
    return [NSAttributedString attributedStringWithString:string];
}

- (NSAttributedString *)info
{
    switch (type) {
        case kCGPDFObjectTypeNull:
        case kCGPDFObjectTypeBoolean:
        case kCGPDFObjectTypeInteger:
        case kCGPDFObjectTypeReal:
        case kCGPDFObjectTypeName:
        case kCGPDFObjectTypeString:
            return [NSAttributedString attributedStringWithString:[self value]];
            
        case kCGPDFObjectTypeArray:
            return [self arrayInfo];
            
        case kCGPDFObjectTypeDictionary:
            return [self dictionaryInfo];
            
        case kCGPDFObjectTypeStream:
            return [self streamInfo];
            
        default:
            return [NSAttributedString attributedStringWithString:@""];
    }
}

- (NSString *)description {
    return [NSString stringWithFormat:@"\n name -> %@ \n type -> %@ \n value -> %@",self.name,self.typeString,self.value];
}

@end
